﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Thread = System.Threading.Tasks;
using Microsoft.Practices.Unity;
using Infrastucture;
using Infrastucture.Converters;
using TimeSplicerModule.Factories;
using TimeSplicerModule.DomainModels;
using TimeSplicerModule.Security;
using TimeSplicerModule.ViewModels;

namespace TimeSplicerModule.Services.DeveloperServices
{
    public interface ISchedulingService : IDisposable
    { 
        void StoreAutoTasksForTheDay();
        void StartAutoTasks();
        void CreateTaskInstances();
    }

    public class SchedulingService : ISchedulingService
    {
        private readonly ITaskFactory _taskFactory;
        private readonly ITaskService _taskService;
        private readonly IStatusFactory _statusFactory;
        private readonly ITaskScheduleFactory _taskScheduleFactory;
        private readonly IScheduledTaskFactory _scheduledTaskFactory;

        private List<TaskModel> _autoStartingTasks;

        public SchedulingService(ITaskFactory taskFactory, ITaskService taskService,
            ITaskScheduleFactory taskScheduleFactory, IScheduledTaskFactory scheduledTaskFactory, IStatusFactory statusFactory)
        {
            _taskFactory = taskFactory;
            _taskService = taskService;
            _taskScheduleFactory = taskScheduleFactory;
            _scheduledTaskFactory = scheduledTaskFactory;
            _statusFactory = statusFactory;
        }

        // so i need to go through all task schedules and select the active schedules
        // the for each create a new scheduled task -- now how do i determine if i should create a new one 
        // or not - this is where scheduled tasks comes in 
        /// <summary>
        /// To be called frequeently to auto start tasks
        /// </summary>
        public void StoreAutoTasksForTheDay()
        {
            // this will be called probably every minute. Since tasks are always getting created at least the day before. I think i can just store all the tasks
            // for the day in memory so save calls
            // so then every minute i can auto start any tasks that are supposed to be autostarted and suspend any active task
            // will also need a timer that clears a pulls new day every early morning.

            _autoStartingTasks = new List<TaskModel>();

            // get schedules that are to be autostarted for the day
            var scheduledTasks = _scheduledTaskFactory.GetList(x => !x.IsCompleted);
            var notStarted = _statusFactory.Get(x => x.Name == "Not Started");

            // TODO: Need to make sure its not started - need to set not started in create task instances as well

            scheduledTasks.ForEach(x =>
                {
                    var task = _taskFactory.Get(t => t.TaskGuid == x.TaskInstanceGuid);

                    if (task.ScheduledDate.Date == DateTime.Today && task.StatusGuid == notStarted.StatusGuid)
                    {
                        // need to get shedule
                        task.Schedule = _taskScheduleFactory.Get(t => t.TaskGuid == task.TaskGuid
                            || t.TaskGuid == task.DerivedFromTaskGuid);

                        if (task.Schedule.AutoStart)
                            _autoStartingTasks.Add(task);
                    }
                });


            // after all scheduled tasks for the day have been retreived need to 

            // not need to auto start tasks - will convert to view model and use task service activate for now
        }

        public void StartAutoTasks()
        {
            // to be checked every minute and if scheduled start is equal or greated then activate
            _autoStartingTasks.ForEach(a =>
                {
                    if (a.ScheduledDate >= DateTime.Now)
                    {
                        var model = ModelConverter.ToModel<TaskViewModel>(a);
                        _taskService.Activate(model);
                    }
                });
        }

        /// <summary>
        /// This is for creating New Instances Of Tasks Records
        /// </summary>
        public void CreateTaskInstances()
        {
            // get active scheules
            var taskSchedules = _taskScheduleFactory.GetList(x => x.IsActive);
            var notStarted = _statusFactory.Get(x => x.Name == "Not Started");
            // for now do not create new instance if one already exists
            // so first check to make sure scheduled instance does not exist

            foreach (var t in taskSchedules)
            {
                // pull back any non-completed scheduled tasks
                var scheduled = _scheduledTaskFactory.Get(x => x.ScheduleGuid == t.ScheduleGuid
                    && !x.IsCompleted);

                if (scheduled == null && hasIncludedDay(t))
                {
                    // cretate new one 
                    int instanceNumber = 1;

                    var previous = _scheduledTaskFactory.GetList(x => x.ScheduleGuid == t.ScheduleGuid);

                    if (previous.Count > 0)
                    {
                        var last = previous.OrderByDescending(o => o.InstanceNumber).FirstOrDefault();
                        instanceNumber = last.InstanceNumber + 1;
                    }
                 
                    var originalTask = _taskFactory.Get(x => x.TaskGuid == t.TaskGuid);

                    // need to create task then scheduled record
                   // use task service save
                    // TODO: SET STATUS GUID TO NOT STARTED
                    var task = new TaskModel
                    {
                        StatusGuid = notStarted.StatusGuid,
                        AssignedToGuid = originalTask.AssignedToGuid,
                        CategoryGuid = originalTask.CategoryGuid,
                        SubCategoryGuid = originalTask.SubCategoryGuid,
                        PriorityGuid = originalTask.PriorityGuid,
                        CreatedByGuid = originalTask.CreatedByGuid,
                        CreatedDate = DateTime.Now,
                        DerivedFromTaskGuid = originalTask.TaskGuid, // i think the derived from task 
                        IsRecurring = originalTask.IsRecurring,
                        IsDerived = true,
                        ProjectGuid = originalTask.ProjectGuid,
                        Name = originalTask.Name,
                        Description = originalTask.Description
                    };

                    // yea i think i can just set a due date and thats it

                    // then i think i need to figure out the start dates and due and other
                    // figure out the date that task should start and end

                    // yeah could us a scheduled start date and an actual start date 
                    // on the task omg so much to think about, this got complicated fast

                    // shared variables
                    int startHour = 0;
                    int endHour = 0;

                    if (t.StartAmPm == Helpers.TimeOfDay.AM)
                        startHour = t.StartHour;
                    else
                        startHour = t.StartHour + 12;



                    if (t.EndAmPm == Helpers.TimeOfDay.AM)
                        endHour = t.EndHour;
                    else
                        endHour = t.EndHour + 12;

                    if (t.OccuranceType == Helpers.TaskScheduleOccuranceTypes.Daily)
                    {
                        // create one for tomorrow 
                        // hmm maybe add completion date on sheduled task to figure out when to start next
                        // create one one day from the start date to the next business day allowed
                        // you could just keep this all simple for now and create the next occurance
                        // and enhance it later

                        // oh yeah here is where i set scheduled and due dates 
        
                        DateTime tomorrow = DateTime.Today.AddDays(1);
                        tomorrow = updateDate(tomorrow, t);

                        DateTime begin = new DateTime(tomorrow.Year, tomorrow.Month, tomorrow.Day, startHour, t.StartMinute, 0);
                        DateTime end = new DateTime(tomorrow.Year, tomorrow.Month, tomorrow.Day, startHour, t.EndMinute, 0);

                        task.ScheduledDate = begin;
                        task.DueDate = end;
                    }
                    else if (t.OccuranceType == Helpers.TaskScheduleOccuranceTypes.Weekly)
                    {
                        // take the start date and goo one week ahead to the next business day in schedule
                        // next week
                        DateTime nextWeek = DateTime.Today.AddDays(7);
                        nextWeek = updateDate(nextWeek, t);

                        DateTime begin = new DateTime(nextWeek.Year, nextWeek.Month, nextWeek.Day, startHour, t.StartMinute, 0);
                        DateTime end = new DateTime(nextWeek.Year, nextWeek.Month, nextWeek.Day, endHour, t.EndMinute, 0);

                        task.ScheduledDate = begin;
                        task.DueDate = end;
                    }
                    else if (t.OccuranceType == Helpers.TaskScheduleOccuranceTypes.Monthly)
                    {
                        // ok so what im going to do is get the last completed one
                        // then increment one month from the started date to the next first instance 
                        // of the weekday its available
                        DateTime nextMonth = DateTime.Today.AddMonths(1);
                        nextMonth = updateDate(nextMonth, t);

                        DateTime begin = new DateTime(nextMonth.Year, nextMonth.Month, nextMonth.Day, startHour, t.StartMinute, 0);
                        DateTime end = new DateTime(nextMonth.Year, nextMonth.Month, nextMonth.Day, endHour, t.EndMinute, 0);

                        task.ScheduledDate = begin;
                        task.DueDate = end;
                    }

                    // then i think i just need to save for the moment
                    task = _taskFactory.Upsert(task);

                    // then create scheduled task record
                    var newScheduled = new ScheduledTaskModel
                    {
                        ScheduledTaskGuid = Guid.NewGuid(),
                        ScheduleGuid = t.ScheduleGuid,
                        InstanceNumber = instanceNumber,
                        IsCompleted = false,
                        TaskInstanceGuid = task.TaskGuid
                    };

                    _scheduledTaskFactory.Upsert(newScheduled);
                }
                else
                {
                    // for now don't schedule since one is stil not completed
                }
            }


            // TOOD: upon task completion need to add call that completes scheduled instance
        }

      


         // i need a function that makes sure the day is allowed - included
        private DateTime updateDate(DateTime date, TaskScheduleModel schedule)
        {
            // make sure that at least one item is checked on the schedule
            if (included(date.DayOfWeek, schedule))
                return date;

            date = date.AddDays(1);
            return updateDate(date, schedule);
        }

        private bool included(DayOfWeek day, TaskScheduleModel schedule)
        {
            switch (day)
            {
                case DayOfWeek.Monday:
                    return schedule.IncludeMonday;

                case DayOfWeek.Tuesday:
                    return schedule.IncludeTuesday;

                case DayOfWeek.Wednesday:
                    return schedule.IncludeWednesday;

                case DayOfWeek.Thursday:
                    return schedule.IncludeThursday;

                case DayOfWeek.Friday:
                    return schedule.IncludeFriday;

                case DayOfWeek.Saturday:
                    return schedule.IncludeFriday;

                case DayOfWeek.Sunday:
                    return schedule.IncludeSunday;

                default:
                    return false;
            }
        }

        private bool hasIncludedDay(TaskScheduleModel schedule)
        {
            if (schedule.IncludeMonday || schedule.IncludeTuesday || schedule.IncludeWednesday || schedule.IncludeThursday || schedule.IncludeFriday
                || schedule.IncludeSaturday || schedule.IncludeSunday)
            {
                return true;
            }

            return false;  
        }


        public void Dispose()
        {
            _autoStartingTasks.Clear();
            _autoStartingTasks = null;
        }
    }
}
